Swift DEMYSTIFYING THE MYSTERIOUS

原文:

HIPSTER SWIFT: DEMYSTIFYING THE MYSTERIOUS


我知道我不是唯一一個有時看著 swift,並自己思考說這是啥鳥東西的人。

Swift 有很多的功能,有很多人並沒使用到,但我知道有一天我得面對這詭異的東西並了解他。

你可能無法從這篇文章中,找到你日常需要特別有用的東西,但誰在乎勒?

我確定會有人喜歡他。

Personally, I’ll be using this post as a quick reference to the hipster stuff you see in Swift sometimes.
Personally, 我將會以一個快速參考(reference)


現在,這篇 po文 會有點長度,但很棒的地方是,你不必一篇接著一篇的看,每個小節各自有他的東西,所以盡情略過去看下一節。

here is a link to each part of this post in case you’re a know-it-all and already know most of what I already wrote about:
為了你的方便,這裡有到各個


@NOESCAPE

這小玩意似乎從 Swift 冒出了蒸氣。

在我了解他之前,每一次看到他,我的眼睛立即被閃了一下。

我告訴我自己這些是 Rob大神(@rob_rix and @cocoaphony) 的工具

但他到底是什麼啊? 原來,他馬該死低有用

他是個 Swift attribute,附在參數(這個參數是個 function)上,如下:

1
2
3
func prepareTheKrakenArmyForBattle(@noescape preparations: () -> Void) {
//Make the preparations to consume the human race.
}

他基本上告訴 compiler,丟進去的 closure 不能再 function 外使用。

因為 closures 是 Swift 的第一公民,你能夠把 closures 丟進變數,並在任何 地點/時間 使用它。

藉由對 closure 標示 @noescape,你告訴 compiler,等等丟進去的 closure 將被執行或 torn down within the body of that function and nowhere else.

用簡單的方式理解的話,大概就是把 function 區塊看作是個監獄,且 closure 會被困在裡面無法逃脫。

這邊有個大圖,展示 closure 試圖逃脫(如你所見,the Swift compiler is having none of it.):

用 @noescape 還有一個很棒加分的地方是,

compiler 會進一步優化妳的 non-escaping code

另一個很棒加分的地方是,在 non escaping closure 裡,你不必顯式捕捉 self(weak 或 其他)。
Since the non-escaping closure will be executed or torn down by the end of the function that its a parameter of,
there is no need to mess with capture semantics for that closure.
This allows for awesome code that looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Kraken {}
class KrakenGeneral {
var soldiers = [Kraken]()

func startTheWar() {
prepareTheKrakenArmyForBattle(punishments: {
soldiers.removeFirst() //notice that we don't need self here.
})
}

func prepareTheKrakenArmyForBattle(@noescape punishments punishments: () -> Void) {
if someoneReportedMisbehavior {
//Someone was misbehaving. Helping out the humans. We can't have that now.
punishments()
}
}
}

TOP


@AUTOCLOSURE

@autoclosure 是另一個你可能看過卻無法理解的 Swift attribute

他蠻類似於 @noescape attribute.

首先,他只能用在 closure parameters,且他會自動附上 @noescape。

@autoclosure attribute 會延遲那些已激活的 function parameter 的執行

基本上,在參數裡呼叫函數將會把這段 function call 包進 closure,for later use in the function body.

舉例說,你會看到很多像這樣的 code :

1
2
3
4
5
6
krakenGeneral.punishSoldier(soldiers.removeFirst())

func punishSoldier(soldier: Kraken) {
throwSoldierInTheBrig(soldier)
sendSoldierHomeToMommy(soldier)
}

這邊,當 krakenGeneral 呼叫 punishSoldier(),removeFirst() 會被執行。

這立即從 soldiers 移除第一個士兵,且回傳剛剛那個士兵,並當作參數再傳入。

接著,你會對違規的 kraken soldier 做些事。

But what if you dont want the first soldier to be removed until later?
但如果你不想馬上移除第一個士兵呢?

如果你是個有同情心的 Kraken Officer?

如果你想要對那位可憐的傢伙再送他回去見他媽之前,再給他一次機會?


回到 @autoclosure 主題。

因為 @autoclosure 會把丟進去的參數包進 closure,我們能延後移除士兵直到我們認為最好 punishSoldier() 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//The removeFirst() function won't get called here.
punishSoldier(soldiers.removeFirst())

//@autoclosure can only be used on closure types. Let's match our parameter to the removeFirst() function type.
func punishSoldier(@autoclosure soldierPunishment: () -> Kraken) {
//Now we can decide when to remove our soldier.
if someoneReportedMisbehavior {
//This will remove the soldier and send that soldier home.
sendSoldierHomeToMommy(soldierPunishment())
} else {
apologizeToSoldierAndFeedHimAHuman()
}
}

At a high level, 上述的 code 與下面的 code 等價:

1
2
3
4
5
6
7
8
9
10
11
12
punishSoldier {
soldiers.removeFirst()
}

func punishSoldier(@noescape soldierPunishment: () -> Kraken) {
if someoneReportedMisbehavior {
//This will remove the soldier and send that soldier home.
sendSoldierHomeToMommy(soldierPunishment())
} else {
apologizeToSoldierAndFeedHimAHuman()
}
}

The main difference in syntax is at the call site.

@autoclosure attribute 不常使用,因為它會讓你的 code 可讀性降低。

在第一個範例,如果我們不知道 punishSoldier() 的內容,我們仍然假設第一個士兵被移除掉。

因為 @autoclosure 把它隱藏了 closure parameter 的實作。

隱藏實作通常絕不是好東西。

個人來說, 我會建議反對使用它,除非他可以說的通他在幹麻。

先前,我提到 @autoclosure 會自動把 closure 變成 non-escaping closure。

如果你在什麼理由下,真的想要 escape closure,你可以附上額外的屬性給 @autoclosure 如下:

1
2
3
4
5
6
7
8
var storageForAClosure: (() -> Bool)?

func executeFunctionWithEscapableClosure(@autoclosure(escaping) houdiniClosure: () -> Bool) {
storageForAClosure = houdiniClosure
}

executeFunctionWithEscapableClosure("kraken".hasPrefix("k"))
storageForAClosure?() //hasPrefix doesn't execute until this line of code

By putting the keyword escaping inside of parentheses to the right of the @autoclosure attribute, you are telling the compiler that the closure parameter is an escaping closure.

TOP


INLINE LAZY VARS

This may not be new to you guys but either way,
I feel this deserves a spot on the Hipster Swift board.
我覺得這值得 spot on the Hipster Swift board.

我確信我們都知道什麼是 lazy variable。

假使你不知道的話, 他基本上是個 instance variable 他只會被初始化一次,並且在你每次存取他時回傳給你 one-time-initialization value。

這對計算敏感的值或物件建立後才能知曉的屬性值是相當有用低。

大多我們可建立像這樣的 lazy variables:

1
2
3
4
5
6
7
class KrakenFoodGenerator {
lazy var lazyHuman: Human = {
//We want a lazy human. Kraken's get annoyed
//when they have to chase their food.
return self.generateWithOptions(.Lazy)
}()
}

Swift 讓 lazy initialization 變得非常簡單,但是當我用 lazy variable 他佔據相當多的空間讓我有點煩惱

Awesomely enough, the good Humans over on the Swift team thought of this for us.

我們可以建立 行內(inline) lazy vars 且減少上述的 code:

1
2
3
class KrakenFoodGenerator {
lazy var lazyHuman: Human = self.generateWithOptions(.Lazy)
}

但… 等等… 有點怪怪的… 我們在變數用了 self 宣告!

他是哪種 self? 他會造成 retain cycle 嗎?

Lazy vars 畢竟只是 closures。

為什麼我不能在附上 lazy keyword 使用 self?!

Well, I can’t really tell you how that’s possible.
嗯,我不能確實告訴你他會發生什麼事。

然而,在寫完 autoclosure 小節後,我能大膽的猜測。

It seems as though anything after the equals sign of a lazy variable is automatically wrapped in a closure for us.
對我們來說 lazy variable 好像在等號後面的所有東西會自動包進 closure。

As for the use of self, it seems as though it comes out unowned automatically for you.
至於使用 self,就好像自動用了 unowned

這邊有在 playground 的一個小測試,告訴你他並不會造成 retain cycle:

Cool, huh? Now you can go and shorten some code. Lord knows that’s exactly what I’m about to do.

TOP


()() - AKA CURRYING

👻 RIP

TOP


THAT WEIRD ELLIPSE THING (VARIADIC PARAMETERS)

TOP


DYNAMIC

dynamic 是宣告用修飾子(declaration modifier),你附在 function 或 變數 的宣告上。

You may see this when using libraries like NewRelic or something similar for app analytics.
當使用像 NewRelic Lib 或類似 app analytics 你可能會看見他們的身影。

What dynamic does is tells the runtime to use dynamic dispatch over static dispatch for the function or variables modified.
dynamic 就是告訴 runtime 對 function 或 變數 使用 動態派送(dynamic dispatch) 而不是 靜態派送(static dispatch)

It also implicitly adds the @objc attribute to the variable or function declaration.
他也顯式的加入 @objc attribute 給變數或函數宣告。

作為一個重要的注意事項,所有使用 dynamic 會以 Objective-C runtime 而不是以 Swift runtime 派送 messages。

現在,我們愛靜態派送。

然而,app 分析並不怎麼喜歡他。

It’s hard to generate and insert analytics code dynamically when functions are mostly static by default.
在預設情況函數大多是靜態,他很難動態產生跟插入分析代碼。

Dynamic is useful for these situations but sacrifices optimizations we get from static dispatch.

dynamic 使用方法如下:

1
2
3
4
5
6
7
class Kraken {
dynamic var imADynamicallyDispatchedString: String

dynamic func imADynamicallyDispatchedFunction() {
//Hooray for dynamic dispatch!
}
}

The other thing you get by using dynamic dispatch is better interoperability with things that use magic in the Objective-C runtime like Core Data that rely on KVC/KVO (think data bindings) at runtime. By using dynamic, you can take advantage of things we have long taken for granted in the Objective-C runtime. You know, just in case Swift wasn’t cool enough for your needs. 😎

Basically, if you know that a certain function or property will be interfered/replaced/swizzled by someone at runtime, then it’s a good idea to mark it dynamic. If you don’t, you could get a crash from the compiler devirtualizing access/inlining implementations (this is an optimization you get for free) to your functions even though other code is trying to use runtime magic that doesn’t play well with those optimizations.

TOP


SPECIAL LITERALS: FILE, LINE, COLUMN, & FUNCTION

AKA Messrs FILE, LINE, COLUMN, & FUNCTION. I think I may be blinded by the underscores.

Joking aside, these are actually pretty cool and when memorized, can save you a lot of time. They are called special literal expressions. Fairly self-explanatory if you ask me; however, I will try to shed some light on it anyways.

Much like numbers (and anything contained in quotes) are number and string literals, respectively, FILE, LINE, COLUMN, & FUNCTION are also literals; as a result, they can be used like any normal literal. As for their return values, here’s a nifty table:

Personally, my favorite one to use is the FUNCTION literal. It’s pretty useful for debugging purposes. For example, how many times have you asked yourself, “Self? Can you tell me where the hell this function is getting called from? It’s not supposed to, you know. That’s gotta be the bug I’m seeing”.

Well, worry no more! When placed as the default value of a parameter, the FUNCTION literal will print the name of the function that called through to the function it’s a part of like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func wreakHavocOnHumanity(krakenArmy: [Kraken], callingFunction: String = __FUNCTION__) {
//There's a bug! There's too much havoc being wreaked!
//Let's figure out who all is calling this function so we can
//fix the bug! We absolutely need to make sure that the
//appropriate amount of havoc be wreaked.
print(callingFunction, "called \(__FUNCTION__)")
}

func startTheWar() {
wreakHavocOnHumanity(krakenArmy)
}

func startEatingDinner(platter: Human) {
wreakHavocOnHumanity(krakenArmy)
}

startTheWar() //prints "startTheWar called wreakHavocOnHumanity"
startEatingDinner(human) //prints "startEatingDinner called wreakHavocOnHumanity"

TOP


LOOP LABELS (EXTENDING BREAK AND CONTINUE)

Don’t knock this one quite yet. At first look, you may be like, “Dammit Hector. I know what breaks and continues are. Stop wasting my time.”

Rudeness aside, Swift takes the break and continue keywords a step further and gives you the ability to label them. Sometimes, you may find that you have nested loop logic that kind of looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for section in sections {
var foundTheMagicalRow = false
for row in rows {
if row.isMagical {
foundTheMagicalRow = true
break
}
}
if foundTheMagicalRow {
//We found the magical row!
//No need to continue looping through our sections now.
break
}
}

Using labels for your loops, you can shorten this code quite a bit. By using a label, you can indicate a specific loop to break or continue. Now here is some pretty code for you which is the exact same as the code above:

1
2
3
4
5
6
7
sectionLoop: for section in sections {
rowLoop: for row in rows {
if row.isMagical {
break sectionLoop
}
}
}

TOP